iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0
Mobile Development

Android Studio 30天學習紀錄系列 第 29

Android Studio 30天學習紀錄-Day29 SurfaceView&Canvas

  • 分享至 

  • xImage
  •  

今天要來實作這兩個功能的結合,那麼首先先加入需要的(手把)依賴至gradle中:

implementation 'io.github.controlwear:virtualjoystick:1.10.1'

那麼就先附上今天UI(FrameLayout用來放SurfaceView、並加入手把JoyStickView):

UI

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </FrameLayout>

    <io.github.controlwear.virtual.joystick.android.JoystickView
        android:id="@+id/joyStick"
        android:layout_width="280dp"
        android:layout_height="160dp"
        android:layout_centerHorizontal="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1.0"
        custom:JV_backgroundColor="#11FFFF"
        custom:JV_borderWidth="1dp"
        custom:JV_fixedCenter="true" />

</androidx.constraintlayout.widget.ConstraintLayout>

SurfaceActivity

首先前置同前幾天一樣,先繼承SurfaceView並實作SurfaceHolder.Callback並繼承裡面的方法跟constructor,
然後加入Canvas需要的畫筆(paint)並設置。

public class SurfaceActivity extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder mSurfaceHolder;
    private Paint paint;
    public SurfaceActivity(Context context) {
        super(context);
        init();
    }
    private void init(){
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        //加入畫筆
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(10);
        paint.setTextSize(32);
    }
    //當Surface創建會呼叫,通常會開啟繪圖的線程(非主線程)
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {

    }
    //當Surface尺寸發生變化則呼叫
    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }
    //當Surface被銷毀會呼叫
    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {

    }
}

接著要進行繪圖時,Canvas有兩個關鍵的方法:

  • SurfaceHolder.lockCanvas() : 返回SurfaceView鎖定的Canvas畫布
  • SurfaceHolder.unlockCanvasAndPost(Canvas canvas) :結束鎖定,提交顯示改變
    而要執行的繪圖指令則是包在中間如下:
//返回鎖定Canvas畫布
canvas = mSurfaceHolder.lockCanvas();
//...繪圖Function執行
//結束鎖定,提交顯示改變
mSurfaceHolder.unlockCanvasAndPost(canvas);

然後通常會加入delay去跑繪圖指令(這邊是用Handler+postDelayed延遲):

Handler mHandler = new Handler();
   //每50ms進行一次繪圖
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            //返回鎖定Canvas畫布
            canvas = mSurfaceHolder.lockCanvas();
            //...繪圖Function執行
            //結束鎖定,提交顯示改變
            mSurfaceHolder.unlockCanvasAndPost(canvas);
            mHandler.postDelayed(this, 50);
        }
    }, 50);

SurfaceActivity程式碼

接著就設計需要的程式碼:

public class SurfaceActivity extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder mSurfaceHolder;
    private Paint paint;
    private Canvas canvas;
    float screenWidth, screenHeight;
    int pos_x,pos_y,move_x=0,move_y=0;

    public SurfaceActivity(Context context) {
        super(context);
        init();
    }

    private void init(){
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        //畫筆
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(10);
        paint.setTextSize(32);
    }
    //繪圖Function,畫30Radius圓圈
    public void drawView(int x , int y){
        Log.d("TAGG","DrawView"+pos_x+"\n"+pos_y);
        canvas.drawCircle(pos_x = pos_x+x,pos_y = pos_y+y,30,paint);
    }
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
        Log.d("TAGG","surfaceCreated");
        //取得螢幕大小
        screenWidth=getWidth();
        screenHeight=getHeight();
        //設置初始位置
        pos_x= (int) (screenWidth*1/4);
        pos_y= (int) (screenHeight*1/4);
        //返回鎖定Canvas畫布
        canvas = mSurfaceHolder.lockCanvas();
        //繪圖Function
        drawView(pos_x,pos_y);
        //改變顯示
        mSurfaceHolder.unlockCanvasAndPost(canvas);
        Handler mHandler = new Handler();
        //每50ms進行一次繪圖
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //返回鎖定Canvas畫布
                canvas = mSurfaceHolder.lockCanvas();
                //繪圖Function
                drawView(move_x, move_y);
                //清零移動量
                move_x = 0;
                move_y = 0;
                //改變顯示
                mSurfaceHolder.unlockCanvasAndPost(canvas);
                mHandler.postDelayed(this, 50);
            }
        }, 50);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.d("TAGG","surfaceChanged");
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
        Log.d("TAGG","surfaceDestroyed");
    }
    //回傳canvas對象
    public Canvas getCanvas(){
        return canvas;
    }
    //從activity設定需要的移動量
    public void setMove_x(int move_x) {
        this.move_x = move_x;
    }

    public void setMove_y(int move_y) {
        this.move_y = move_y;
    }
}

MainActivity

  • 加入Surface的頁面至FrameLayout上
//onCreate...{
surfaceActivity = new SurfaceActivity(this);
fl = findViewById(R.id.frameLayout);
//將surfaceView加入至FrameLayout上
fl.addView(surfaceActivity);
//
  • 加入搖桿的監聽器(setOnMoveListener),若紅字則注意一下Gradle是否加入搖桿的依賴!
joystickView=findViewById(R.id.joyStick);
//搖桿監聽器
joystickView.setOnMoveListener(new JoystickView.OnMoveListener() {
    @Override
    public void onMove(int angle, int strength) {

    }
});

MainActivity程式碼

接著設計需要的MainActivity程式碼。

public class MainActivity extends AppCompatActivity {
    SurfaceActivity surfaceActivity;
    FrameLayout fl;
    Canvas canvas;
    //搖桿
    JoystickView joystickView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        surfaceActivity = new SurfaceActivity(this);
        fl = findViewById(R.id.frameLayout);
        //將surfaceView加入至FrameLayout上
        fl.addView(surfaceActivity);
        joystickView=findViewById(R.id.joyStick);
        //搖桿監聽器
        joystickView.setOnMoveListener(new JoystickView.OnMoveListener() {
            @Override
            public void onMove(int angle, int strength) {
                //取得surfaceView的Canvas
                canvas = surfaceActivity.getCanvas();
                //搖桿angle為
                //     90度
                //180        0
                //     270
                if(angle>0&&angle<=90){
                    //計算x y移動量
                    int x = (int) (10-(angle/9));
                    int y = (int) -(angle/30);
                    //設定x y移動量
                    surfaceActivity.setMove_x(x);
                    surfaceActivity.setMove_y(y);
                }
                else if(angle>90&&angle<=180){
                    //計算x y移動量
                    int x = (int) ((180-angle)/9)-10;
                    int y = (int) -((180-angle)/30);
                    //設定x y移動量
                    surfaceActivity.setMove_x(x);
                    surfaceActivity.setMove_y(y);
                }
                else if(angle>180&&angle<=270){
                    //計算x y移動量
                    int x = (int) -((270-angle)/9);
                    int y = (int) ((angle-180)/30);
                    //設定x y移動量
                    surfaceActivity.setMove_x(x);
                    surfaceActivity.setMove_y(y);
                }
                else{
                    //計算x y移動量
                    int x = (int) ((angle-270)/9);
                    int y = (int) ((360-angle)/30);
                    //設定x y移動量
                    surfaceActivity.setMove_x(x);
                    surfaceActivity.setMove_y(y);
                }
            }
        });
    }
}

成果

https://ithelp.ithome.com.tw/upload/images/20221011/20139259KdXOUepn9e.jpg
https://ithelp.ithome.com.tw/upload/images/20221011/20139259fYA5BX5Fld.jpg


上一篇
Android Studio 30天學習紀錄-Day28 Canvas繪圖
下一篇
Android Studio 30天學習紀錄-Day30 結語
系列文
Android Studio 30天學習紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言